<!doctype html>
<html lang="zh" class="h-100">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="stylesheet" href="assets/css/bootstrap.min.css">
    <link rel="stylesheet" href="assets/css/style.css">

    <title>.md</title>
  </head>
  <body class="h-100">
  <div class="container-fluid h-100 p-2">
  	<p>UDP数据包是有界的，不存在数据包边界确定的问题，但TCP数据包是无界的，如果使用的自定义格式数据包，就需要自定义协议，一般来说，自定义协议主要有两个工作：</p><h4>Parser</h4><p>完成数据包的解析</p><h4>Handler</h4><p>网络事件处理</p><p>以一个简单的echo server为例子说明整个过程：</p><ol><li>客户端连接后，即可发送文本内容，文本内容以CRLF结尾，服务器收到数据包后，向所有用户广播当前用户输入的内容。</li><li>客户端发送字符串quit表示断开连接，服务器将通知事件发送给其它客户端。</li><li>单个数据包如果超过64KB, 给客户端发送错误信息。</li></ol><p>这部分代码已经包含在源码包中：  </p><p>vendor/beyoio/beyoio/protocol/text/Parser.php中的内容：</p><pre><code>&lt;?php
namespace beyod\protocol\text;

use beyod\Connection;

class Parser extends \beyod\Parser 
{  
    //数据包结尾定界符
    public $delimiter = &quot;\r\n&quot;;
    
    public $max_packet_size = 65536; //设置数据包最大长度
    
    /** 
     * @param string $buffer
     * @param Connection $connection 当前连接,对udp协议此值为null
     * @return int
     */
    public function input($buffer, $connection){
        
        //调用父类方法，检测数据包是否过大
        $len = parent::input($buffer, $connection);
        
        //如果数据中没有CRLF, 返回0，说明完整数据未收到，继续等待下一次数据段到达。
        $pos = strpos($buffer, $this-&gt;delimiter);
        if ($pos === false) {
            return 0;
        }
        
        //包含CRLF， 说明已经收到完整数据包，返回整个数据包的长度。
        return $pos + 2;
    }
    
    /**
    * 一旦收到数据包后，可以解码处理，因为对我们来说，是不必要关心的，所以清理后返回给handler
    */
    public function decode($buffer, $connection){
        return trim($buffer);
    }
    
    //向客户端发送文本时，自动追加边界符。
    public function encode($buffer, $connection){
        return $buffer.$this-&gt;delimiter;
    }
}
</code></pre><p>vendor/beyoio/beyoio/protocol/text/Handler.php</p><pre><code>&lt;?php

namespace beyod\protocol\text;

use Yii;
use yii\base\Event;
use beyod\IOEvent;
use beyod\MessageEvent;
use beyod\CloseEvent;
use beyod\ErrorEvent;

class Handler extends \beyoio\Handler
{
    public $tag = null;
    
    public function init()
    {
        $this-&gt;tag = &quot;Beyod Text Echo Server 1.0.1&quot;
            .&quot;\r\nServer:\t&quot;.Yii::$app-&gt;server-&gt;server_token
            .&quot;\r\nServer Start At:\t&quot;.date('Y-m-d H:i:s')
            .&quot;\r\nGPID:\t&quot;.Yii::$app-&gt;server-&gt;getGPID()
            . &quot;\r\n&quot;;
    }
    
    
    /**
     * @param  IOEvent $event
     * {@inheritDoc}
     * @see \beyod\Handler::onConnect()
     */
    public function onConnect(IOEvent $event)
    {
        //连接后向当前客户端发送提示信息
        $resp = $this-&gt;tag.&quot;your connection id &quot;.$event-&gt;sender.&quot;\r\nPlease input message, quit to disconnect:\r\n&quot;;
        $event-&gt;sender-&gt;send($resp);
        
        //向其它连接正常的客户端发送上线通知
        foreach($event-&gt;sender-&gt;listenner-&gt;connections as $id =&gt; $conn){
            if($id == $event-&gt;sender-&gt;id || $conn-&gt;isClosed() ) continue;
            $conn-&gt;send($conn.&quot; connected&quot;);
        }
    }
    
    /**
     * 当收到数据包时的处理
     * @param  MessageEvent $event
     */
    public function onMessage(MessageEvent $event)
    {
        //如果收到quit，向其它客户端发送上线通知
        if($event-&gt;message == 'quit'){            
            foreach($event-&gt;sender-&gt;listenner-&gt;connections as $id =&gt; $conn){
                if($id == $event-&gt;sender-&gt;id || $conn-&gt;isClosed() ) continue;
                $conn-&gt;send($conn.' quit !');
            }
            
            //向当前客户端发送消息并断开连接
            return $event-&gt;sender-&gt;close('bye bye !');
        }
        
        //向其它客户端发送消息内容，实现消息群聊
        foreach($event-&gt;sender-&gt;listenner-&gt;connections as $id =&gt; $conn){
            if($id == $event-&gt;sender-&gt;id || $conn-&gt;isClosed() ) continue;
            $conn-&gt;send($conn.&quot;: &quot;.$event-&gt;message);
        }
        
        //当前客户端的提示
        $event-&gt;sender-&gt;send(&quot;you said: &quot;.$event-&gt;message);
    }
    
    //当连接断开时，向其它客户端发送下线通知
    public function onClose(CloseEvent $event){
        foreach($event-&gt;sender-&gt;listenner-&gt;connections as $id =&gt; $conn){
            if($id == $event-&gt;sender-&gt;id || $conn-&gt;isClosed() ) continue;
            $conn-&gt;send($conn.&quot; disconnected&quot;);
        }
    }
    
    //当数据包解析错误时，向客户端发送错误消息（并未断开连接）
    public function onBadPacket(ErrorEvent $event)
    {
        $event-&gt;sender-&gt;send($event-&gt;errstr);
    }
}
</code></pre><p>config/main.php中的配置此服务功能：</p><pre><code>'components' =&gt;[
    'server' =&gt; [
        //...
        'listeners' =&gt; [
            'text' =&gt; [
                    'class' =&gt; 'beyod\Listener',
                    'listen' =&gt; 'tcp://0.0.0.0:7723',
                    'parser' =&gt;  'beyod\protocol\text\Parser',
                    'handler' =&gt; 'beyod\protocol\text\Handler'
                ],
            //其它服务协议
        ]
    ]
]
</code></pre><p>同时打开多个telnet，连接至7723端口即可测试。</p>  </div>
  <script src="assets/js/jquery.min.js"></script>
  <script src="assets/js/popper.min.js"></script>
  <script src="assets/js/bootstrap.min.js"></script>

<script>
$(function(){
  var url = self.location;
  parent.$('a').removeClass('font-weight-bold');
  parent.$('a[href="18.html"]').addClass('font-weight-bold');
});
</script>  
  
  </body>
</html>